# 加入用户特征，计算pearson相似度和用户特征相似度，
# 选取不同权值 weight ，用于确定最终的用户相似度
# 用于解决数据稀疏和冷启动问题###
from math import sqrt

import mystart
from start import getAverage
import heapq
from start import sim_pearson


# 计算引入用户特征后的相似度
# userA, userB分别表示用户A、B的id
# profileSet表示需要比较的用户特征集合
# userProfile 是用户画像
def simUserProfile(userProfile, userA, userB):
    sumProf = 0.0
    sumProf += pow(userProfile[userA]['age'] - userProfile[userB]['age'], 2)
    if userProfile[userA]['occupation'] != userProfile[userB]['occupation']:
        sumProf += pow(10, 2)    # 职业不同 相差类似10岁
    if userProfile[userA]['sex'] != userProfile[userB]['sex']:
        sumProf += pow(5, 2)  # 性别不同 相差类似5岁
    return 1.0 / (1 + sqrt(sumProf))


# 皮尔森相似度 和 用户特征相似度 共同确定最终的相似度
def finalSimUser(prefer, profile, userAId, userBId):
    simPearson = sim_pearson(prefer, userAId, userBId)
    simProfile = simUserProfile(profile, userAId, userBId)
    weight = 0.6
    return simPearson * weight + (1 - weight) * simProfile


### 获取对item评分过的并且与当前person用户 最相似的k个用户（K默认20）
def topKMatches(prefer, profile, person, itemId, k):
    userSet = []
    # 找出所有prefer中评价过Item的用户,存入userSet
    for user in prefer:
        if itemId in prefer[user]:
            userSet.append(user)
    # 计算相似性 相似用户计算过程中 不加入当前用户 相似度计算选用引入用户特征+皮尔森相关系数法
    scores = [(finalSimUser(prefer, profile, person, other), other) for other in userSet if other != person]
    rs = heapq.nlargest(k, scores)  # 用最大堆求最大的k个元素
    return rs


# 读取用户画像
def loadProfile():
    user_profile = {}
    # 默认sep = '\t'，拿tab作为分列符
    for line in open("ml-100k/u.csv", 'r'):  # 打开指定文件
        (uId, age, sex, occupation, zipcode) = line.split('|')  # 数据集中每行有4项
        user_profile.setdefault(uId, {})
        user_profile[uId] = {'age': int(age), 'sex': sex, 'occupation': occupation}
    return user_profile

##==================================
#         加载指定的训练集文件
#  参数fileName 代表某个训练集文件
##==================================
def loadMovieLensTrain(fileName):
    prefer = {}
    for line in open(fileName, 'r'):       # 打开指定文件
        (userid, movieid, rating, ts) = line.split('\t')     # 数据集中每行有4项
        prefer.setdefault(userid, {})      # 设置字典的默认格式,元素是user:{}字典
        prefer[userid][movieid] = float(rating)
    return prefer

### 平均加权策略，预测userId对itemId的评分
def getRating(prefer1, profile, userId, itemId, knumber):
    # 获取K近邻用户(评过分的用户集) 最相似的k个用户
    users = topKMatches(prefer1, profile, userId, itemId, k=knumber)
    jiaquanAverage = 0.0
    simSums = 0.0
    # 计算每个用户的加权，预测
    for other in users:
        sim = other[0]  # 计算比较其他用户的相似度
        otherId = other[1]
        averageOther = getAverage(prefer1, otherId)  # 该用户的平均分
        # 累加
        simSums += abs(sim)  # 取绝对值
        jiaquanAverage += (prefer1[otherId][itemId] - averageOther) * sim  # 累加，一些值为负

    # 获取userId 的平均值 该用户给所有项目打分的均值
    averageOfUser = getAverage(prefer1, userId)
    # simSums为0，即该项目尚未被其他用户评分，这里的处理方法：返回用该户平均分
    if simSums == 0:
        return averageOfUser  # todo 这里需要改进 ！！！！
    else:
        return averageOfUser + jiaquanAverage / simSums


##==================================================================
##     getAllUserRating(): 获取所有用户的预测评分，存放到fileResult中
##
## 参数:fileTrain,fileTest 是训练文件和对应的测试文件，fileResult为结果文件
##     similarity是相似度度量方法，默认是皮尔森。
##==================================================================
def getAllUserRating(fileTrain, fileTest, fileResult, k):
    preferTrain = loadMovieLensTrain(fileTrain)  # 加载训练集 训练集的偏好矩阵
    preferTest = loadMovieLensTrain(fileTest)  # 加载测试集
    profile = loadProfile()  # 加载用户画像

    ##===================================================
    ##设定阀值选取评分高于3的用户可能感兴趣的项目向其推荐
    ##===================================================
    # 文件操作
    inAllnum = 0
    file = open(fileResult, 'a')
    # file.write("%s\n" % "用户id----项目ID------预测评分------实际评分-------")
    for userid in preferTest:  # test集中每个用户
        for item in preferTest[userid]:  # 对于test集合中每一个项目用base数据集,CF预测评分
            rating = getRating(preferTrain, profile, userid, item, k)  # 基于训练集预测用户评分(用户数目<=K)
            # if rating > 3: # 改成大于用户的平均评分 就 推荐给他
            if rating > getAverage(preferTrain, userid):  # 改成大于用户在训练集中的的平均评分 就推荐给他
                file.write('%s\t%s\t%s\t%s\n' % (userid, item, rating, preferTest[userid][item]))
                inAllnum = inAllnum + 1
    file.close()
    print("-------------Completed!!---append to %s  %d 条数据" % (file.name, inAllnum))


if __name__ == '__main__':
    print(u"\n--------------start... -----------\n")
    k = 60
    fileName = 'recommend/recommend-add-profile-' + str(k) + '.txt'
    MAE = mystart.getMAE(mystart.loadRecommendAbs(fileName))
    print("MAE : %f \n" % MAE)

    for i in range(1, 6):  # 多个测试集和训练集选取
        baseName = 'u' + str(i) + '.base'
        testName = 'u' + str(i) + '.test'
        getAllUserRating('ml-100k/' + baseName, 'ml-100k/' + testName, fileName, k)  # 输出文件
        mystart.testEvaluate(fileName, baseName, testName)
    print(u"\n--------------end... -----------\n")
